﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Web;
using Entidades;
using Entidades.Models.Sistema;
using Common;
using Import;
using Import.Exceptions;
using Web.Handlers.Data;
using Web.Helpers;
using Web.Pages;
using Controladores;
using Entidades.Models;

namespace Web.Handlers
{
    /// <summary>
    /// Summary description for ScriptFileUploadHandler
    /// </summary>
    public class ScriptFileUploadHandler : IGenericHandler
    {
        public class FileExtensions
        {
            public const string Doc = ".doc";
            public const string Docx = ".docx";
        }

        public class FileTypes
        {
            public const string Microsoft_Word_Doc = "application/msword";
            public const string OpenXml_Doc = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
        }

        public class ImportParameters
        {
            public int IdProject { get; set; }
            public int IdUser { get; set; }
            public bool OmitFirstRow { get; set; }
            public int? IdScript { get; set; }
        }

        /// <summary>
        /// Processes Http Request
        /// </summary>
        /// <param name="context"></param>
        public void ProcessRequest(HttpContext context)
        {
            var respuestaInfo = default(RespuestaInfo);
            var importProject = default(ImportProject);
            try
            {
                var importData = new ImportData(context);
                if (importData.FileInfo != null)
                {
                    // Validate file type
                    if (!ValidateFileType(importData.FileInfo.FileType))
                        throw new Exception(String.Format("El archivo seleccionado no es un documento de Word ({0}/{1}).", FileExtensions.Doc, FileExtensions.Docx));

                    importProject = ScriptHelper.GetImportCurrentState(context, importData.FileInfo.FileName);

                    importProject.Data.FileInfo = importData.FileInfo;
                    ScriptHelper.SaveImportCurrentState(context, importProject, ImportStep.ReadFileContent);

                    // Get import parameters
                    var importParams = GetImportParams(context);
                    importProject.Data.IdScript = importParams.IdScript;
                    importProject.Data.IdProject = importParams.IdProject;
                    ScriptHelper.SaveImportCurrentState(context, importProject, ImportStep.ReadParameters);

                    // Merge import settings
                    var importSettings = new ImportSettings();
                    MergeImportSettings(importSettings, importParams);
                    importProject.Settings = importSettings;
                    ScriptHelper.SaveImportCurrentState(context, importProject, ImportStep.MergeSettings);

                    // Process request
                    respuestaInfo = ProcessImportRequest(importProject, context);
                }
            }
            catch (System.IO.IOException ex)
            {
                BasePage.LogError(ex);

                respuestaInfo = new RespuestaInfo(false, ex.Message);
            }
            catch (Exception ex)
            {
                BasePage.LogError(ex);

                respuestaInfo = ScriptHelper.GetResponseInfoWithExceptionData(ex);
            }
            finally
            {
                if (importProject != null && importProject.Data != null)
                {
                    var steps = new ImportStep[] { ImportStep.SearchTranslator };
                    if (!steps.Contains( importProject.StepInfo.Step))
                    {
                        DeleteTemporaryFiles(importProject.Data.UploadDirectory);
                    }
                    else
                    {
                        // ClearImportCurrentState(context);
                    }
                }

                context.Response.Write(Formatter.JSON<RespuestaInfo>.Serialize(respuestaInfo));
            }
        }

        /// <summary>
        /// Is Reusable?
        /// </summary>
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }

        #region Private Methods

        private static bool IsScriptException(Exception ex)
        {
            var isScriptException = false;
            if (ex != null)
            {
                isScriptException = ex.GetBaseException().GetType() == typeof(ScriptImportException);
            }
            return isScriptException;
        }

        private static ImportParameters GetImportParams(HttpContext context)
        {
            var idUser = Helpers.SessionHelper.GetCurrentSessionInfo(context).User.Id;
            var idProject = Convert.ToInt32(context.Request.Form["IdProject"]);
            var omitFirstRow = Convert.ToBoolean(context.Request.Form["OmitFirstRow"]);
            var idScript = context.Request.Form["IdScript"];

            var importParams = new ImportParameters()
            {
                IdProject = idProject,
                OmitFirstRow = omitFirstRow,
                IdScript = idScript != null ? Convert.ToInt32(idScript) : new int?(),
                IdUser = idUser
            };

            return importParams;
        }

        private static RespuestaInfo ProcessImportRequest(ImportProject importProject, HttpContext context)
        {
            var respuestaInfo = default(RespuestaInfo); 

            // Create temp. directory
            importProject.Data.UploadDirectory = ScriptHelper.GetUploadDirectory(String.Format("{0}-{1}", DateTime.Now.ToString("yyyyMMddHHmmss"), importProject.Settings.UserId));
            var directoryInfo = Helpers.FileHelper.CreateDirectory(importProject.Data.UploadDirectory);
            if (!directoryInfo.Exists)
                throw new Exception("No se pudo crear el directorio temporal para procesar los archivos.");

            // Save file to temp. directory
            importProject.Data.SavedFileName = Helpers.FileHelper.SaveFile(importProject.Data.FileInfo.FileContent, importProject.Data.UploadDirectory, importProject.Data.FileInfo.FileName);
            ScriptHelper.SaveImportCurrentState(context, importProject, ImportStep.SaveFile);

            // [IF .DOC] Convert to .DOCX
            var fileInfo = new FileInfo(importProject.Data.FileInfo.FileName);
            if (fileInfo.Extension == FileExtensions.Doc)
            {
                importProject.Data.SavedFileName = ConvertDocumentToOpenXmlFormat(importProject.Data.SavedFileName);
                ScriptHelper.SaveImportCurrentState(context, importProject, ImportStep.ConvertFile);
            }

            // Check if Translator needs to be selected by User
            var sourceDocumentInfo = ScriptHelper.GetDocumentHeaderData(importProject.Data.SavedFileName, importProject.Settings);
            var translatorNames = GetTranslatorFromDocument(sourceDocumentInfo.Translator);
            importProject.HeaderDocumentInfo = sourceDocumentInfo;
            ScriptHelper.SaveImportCurrentState(context, importProject, ImportStep.ReadHeaderData);

            var currentTranslatorFirstName = translatorNames[0];
            var currentTranslatorLastName = translatorNames[1];

            if (importProject.Data.IdScript.HasValue)
            {
                var guion = new ControladorBase<Guion>().ObtenerEntidad(x => x.Id == importProject.Data.IdScript.Value);
                importProject.Data.IdTranslator = guion.Traductor.Id;
                importProject.Data.TranslatorFirstName = guion.Traductor.Nombre;
                importProject.Data.TranslatorLastName = guion.Traductor.Apellido;
            }

            // If Translator has already chosen and matches the same Firstname & Lastname as in the Document.. continue process
            if (importProject.Data.IdTranslator.HasValue
                && CompareTexts(importProject.Data.TranslatorFirstName, currentTranslatorFirstName)
                && CompareTexts(importProject.Data.TranslatorLastName, currentTranslatorLastName))
            {
                ScriptHelper.SaveImportCurrentState(context, importProject, ImportStep.ReadyForImport);

                respuestaInfo = ScriptHelper.ContinueImportProcess(importProject, context);
            }
            else
            {
                // Else.. force User to select Translator
                var translators = SearchForMatchingTranslators(currentTranslatorFirstName, currentTranslatorLastName);
                importProject.StepInfo.Data = translators;
                importProject.StepInfo.Message = String.Format("Se ha(n) encontrado <b>{0} Usuario(s)</b> con coincidencias en Nombre y/o Apellido con '<b>{1}</b>'.<br /><br />Por favor, seleccione el que corresponda:", translators.Count, sourceDocumentInfo.Translator);

                ScriptHelper.SaveImportCurrentState(context, importProject, ImportStep.SearchTranslator);

                respuestaInfo = new RespuestaInfo(true, "Debe indicar el Traductor que corresponde.", importProject.StepInfo);
            }

            return respuestaInfo;
        }

        private static string[] GetTranslatorFromDocument(string translator)
        {
            //var title = sourceDocumentInfo.TranslatedTitle; // TODO => TranslatedTitle / OriginalTitle ?
            //var episode = sourceDocumentInfo.Episode;
            var translatorFirstName = String.Empty;
            var translatorLastName = String.Empty;
            var translatorNames = translator.Split(' ');
            if (translatorNames.Length > 0)
            {
                translatorFirstName = translatorNames.Length > 0 ? translatorNames[0] : String.Empty;
                translatorLastName = translatorNames.Length > 1 ? translatorNames[1] : String.Empty;
            }
            return new string[] {translatorFirstName, translatorLastName};
        }

        private static List<UsuarioInfo> SearchForMatchingTranslators(string firstName, string lastName)
        {
            var currentSession = SessionHelper.GetCurrentSessionInfo();

            var first = firstName.Trim().ToLower();
            var last = lastName.Trim().ToLower();

            var usuarios = BasePage.ObtenerUsuariosConVinculoActivo(currentSession.Studio.Id);
                usuarios = usuarios.Where
                    (
                        //x => x.Tipo.Id == WellKnownKeys.Entities.TiposUsuario.TRADUCTOR.Id
                        x => 
                            (
                                ( CompareTexts(first, x.Nombre) || CompareTexts(first, x.Apellido) )    // either <first> is contained in FirstName or LastName
                                ||                                                                      // OR
                                ( CompareTexts(last, x.Apellido) || CompareTexts(last, x.Nombre) )      // <last> is contained in FirstName or LastName
                            )
                    ).OrderByDescending(x => x.Tipo.Nombre).ToList(); // Likely Translators will be listed before (first) than Actors

            return usuarios.Select(x => new UsuarioInfo(x)).ToList();
        }

        private static bool CompareTexts(string name1, string name2)
        {
            return  !String.IsNullOrEmpty(name1) &&
                    !String.IsNullOrEmpty(name2) &&
                    name2.Trim().ToLower().RemoveDiacritics().Contains(name1.Trim().ToLower().RemoveDiacritics());
        }

        private static bool ValidateFileType(string contentType)
        {
            var validFileTypes = new string[]
            {FileTypes.Microsoft_Word_Doc, FileTypes.OpenXml_Doc};

            return validFileTypes.Contains(contentType.ToLower());
        }

        private static void DeleteTemporaryFiles(string path)
        {
            if ( !String.IsNullOrEmpty(path) )
                Helpers.FileHelper.DeleteDirectory(path);
        }

        private static string ConvertDocumentToOpenXmlFormat(string inputFilename)
        {
            // TODO => make this config key a Constant
            var appRoot = ConfigurationManager.AppSettings["appRoot"];
            const string binariesDirectory = "bin";
            const string converterDirectory = "OpenXmlConverter";
            const string converterFilename = "doc2x.exe";

            // TODO => Use Path.Combine
            var converterToolFileName = Helpers.FileHelper.GetServerFilename(String.Format(@"{0}\{1}\{2}\{3}", appRoot, binariesDirectory, converterDirectory, converterFilename));

            //BasePage.LogMessage( converterToolFileName );

            var convertedFileName = inputFilename.Replace(FileExtensions.Doc, FileExtensions.Docx);
            Helpers.FileHelper.RunExecutable(converterToolFileName, new string[] { inputFilename, convertedFileName });
            return convertedFileName;
        }

        private static void MergeImportSettings(ImportSettings importSettings, ImportParameters importParams)
        {
            // TODO: Si los parámetros no cambian (al menos no frecuentemente), ¿qué sentido tiene hace todo este mapeo en cada upload?
            // TODO: la property .Header.Index debería moverse a cada valor. Es decir KeywordTableIndex, EpisodeTableIndex, ProjectTableIndex, etc.

            var parametrosImportacion = new ControladorBase<ParametroImportacion>().ObtenerEntidad(x => x.Id == 1);

            // General
            importSettings.UserId = importParams.IdUser;

            // Validations
            importSettings.Validations[ImportSettings.ImportValidations.MissingTimeCode] = parametrosImportacion.ValidacionTimeCodeFaltante;
            importSettings.Validations[ImportSettings.ImportValidations.WrongTimeCodeFormat] = parametrosImportacion.ValidacionTimeCodeFormatoIncorrecto;
            importSettings.Validations[ImportSettings.ImportValidations.WrongTimeCodeSequence] = parametrosImportacion.ValidacionTimeCodeSecuenciaIncorrecta;
            importSettings.Validations[ImportSettings.ImportValidations.EmptyRow] = parametrosImportacion.ValidacionFilaVacia;

            // Header
            importSettings.TablesInfo.Header.Keyword = new CellInfo(parametrosImportacion.HabilitadoClave, parametrosImportacion.TablaClave, parametrosImportacion.FilaClave, parametrosImportacion.ColumnaClave);
            importSettings.TablesInfo.Header.RunningTime = new CellInfo(parametrosImportacion.HabilitadoDuracion, parametrosImportacion.TablaDuracion, parametrosImportacion.FilaDuracion, parametrosImportacion.ColumnaDuracion);
            importSettings.TablesInfo.Header.Episode = new CellInfo(parametrosImportacion.HabilitadoEpisodio, parametrosImportacion.TablaEpisodio, parametrosImportacion.FilaEpisodio, parametrosImportacion.ColumnaEpisodio);
            importSettings.TablesInfo.Header.Project = new CellInfo(parametrosImportacion.HabilitadoProyecto, parametrosImportacion.TablaProyecto, parametrosImportacion.FilaProyecto, parametrosImportacion.ColumnaProyecto);
            importSettings.TablesInfo.Header.OriginalTitle = new CellInfo(parametrosImportacion.HabilitadoTituloOriginal, parametrosImportacion.TablaTituloOriginal, parametrosImportacion.FilaTituloOriginal, parametrosImportacion.ColumnaTituloOriginal);
            importSettings.TablesInfo.Header.TranslatedTitle = new CellInfo(parametrosImportacion.HabilitadoTituloTraducido, parametrosImportacion.TablaTituloTraducido, parametrosImportacion.FilaTituloTraducido, parametrosImportacion.ColumnaTituloTraducido);
            importSettings.TablesInfo.Header.Translator = new CellInfo(parametrosImportacion.HabilitadoTraductor, parametrosImportacion.TablaTraductor, parametrosImportacion.FilaTraductor, parametrosImportacion.ColumnaTraductor);

            // TODO => is this correct?
            if ( parametrosImportacion.HabilitadoAnotacionActor )
                importSettings.Annotations[ImportSettings.ImportAnnotations.Actor] =  parametrosImportacion.AnotacionActor.Split(' ');
            if ( parametrosImportacion.HabilitadoAnotacionDirector )
                importSettings.Annotations[ImportSettings.ImportAnnotations.Director] = parametrosImportacion.AnotacionDirector.Split(' ');
            if ( parametrosImportacion.HabilitadoAnotacionTimeCodeResetHora )
                importSettings.Annotations[ImportSettings.ImportAnnotations.HourReset] = new string[] { parametrosImportacion.AnotacionTimeCodeResetHora };

            // Content
            importSettings.TablesInfo.Content.OmitFirstRow = importParams.OmitFirstRow;
            importSettings.TablesInfo.Content.Index = 2;
            importSettings.TablesInfo.Content.TimeCode.ColIndex = 1;
            //importSettings.TablesInfo.Content.TimeCode.RowIndex = 3;
            importSettings.TablesInfo.Content.Character.ColIndex = 2;
            //importSettings.TablesInfo.Content.Character.RowIndex = 3;
            importSettings.TablesInfo.Content.Dialogue.ColIndex = 3;
            //importSettings.TablesInfo.Content.Dialogue.RowIndex = 3;

            // Loop Parsers
            if ( parametrosImportacion.HabilitadoLoopsPorPalabras )
                importSettings.LoopParsers.Add( ImportSettings.LoopParserTypes.ByWords, parametrosImportacion.PalabrasPorLoop );
            if ( parametrosImportacion.HabilitadoLoopsPorSegundos )
                importSettings.LoopParsers.Add( ImportSettings.LoopParserTypes.ByTime, parametrosImportacion.SegundosPorLoop );
        }

        #endregion
    }
}